#!/usr/bin/python
# -*- coding: UTF-8 -*-

import wx
import sys
import os
import socket
import signal
import time
import cStringIO
import xml.dom.minidom
import wx.lib.wordwrap
import urllib2
import pickle
import wx.py.shell

version = "0.0.9"
name = "wxSopcast"
icon_image = '\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00 \x00\x00\x00 \x08\x06\x00\
\x00\x00szz\xf4\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\x00\x00\x01\
\x94IDATX\x85\xb5WAn\xc4 \x0c\xf4@\x7f\xd2>\xa5y5}J\xbe\x92\xb8\x87\r\xac\
\x01c\x02dGZiE\x103L\x06\x9b\x00\xce\x93\x05>\x0f\x8e\xff\xe1<\xcc\xc9\x13\
\xf8\xea\x913\'~\x02\xc0O\x8bh\n(\xc9\x89\x88\x98\x99\x00d\x83\xab\x82T\x01\
\x99\xed@"\x97B\xc4\xf3%W\\I\xcc\xe7\xc1\x14\xc4\x18s"\x04@\x9a+R\xf0(\x92\
\x03%1\x05\x9d0\n\x89\xe4\xab0C\x18E\x94x\x828\x02p\xbe\xde\xbd\x85\xad\xca\
\x00Qx\x8d\xcfd\xc1\xf5\xa7\x14\xb8\\\x89\xbf$<\xd0T\x16\x10\x0b\xd1\x90\x0b\
-L\xb80\xee\x80\x85\t\x17\x92\x008\x0f\xda\x88h3f[\xcf&\x91\x9d\x028\x8f\xfd\
<\xf8\xbbE\xb4\xfa\x8a\x14@6\xa3\xfd<\xf8\xc7x\x87\x99\xbd\x96\x18\x91\x85^3\
K\x0e\xf4\xc8\xcb\x05x\xbb\x166\x84TUu;\xaa\xb2=\x1dB8\x8f\x94\x1bcN\x96+%\
\xa4\x8e\xe8\xbd\xfb}0\xc1\xaf\x8e\xa9<\x10D=\xa1\xe8]H\xee\x92\xe3O\x99Tf!\
\xd4\xe3\xcf\xd6\x01\x03-\x17\x86\x05\xc4\x96]Z\xcf\xbf\xca\xe4Va\x12\xe3\
\xb7\x05hw\x85Qh.\xd8\xedX\x90\x7f\xa2\x08\x11\xddp\xe0\x93\xe4]\x01#\xe4\
\xea)\x90\xeb\xcc\x08P1\xd3\x90B;C\xe6\xb5\xbc\xda\xbd8\xbf\xc0\xfb$X\xbb\
\x97"4,\xd7\x01\x93\xbc\xe5V\xb6\x91F%l\xbe\x7fQ\xd7\xbb\xb8\x88\xca\x0c\xc8\
\x86t\xeb\x18f\x18\xb8\xbc\xa6]\x1a]\xd6\xec\x05\xc3GPX~\xf7n\xd8mF\xc9\xbe\
\x96\x90\t\xd2!\x01\x95\x90r\x81\xc5\x8f\xd3\x7f\xa5\x8a\t\xa7\x8d\x89\xeak\
\x00\x00\x00\x00IEND\xaeB`\x82'

class Config:
    def __init__(self):
        self.channel_list_url = "http://channel.sopcast.com/gchlxml"
        self.player = "mplayer -ontop -zoom -ao pulse -softvol"
        self.sop = "sp-sc"
        self.my_channels = {"sop://broker.100vod.com:3912/1018":"凤凰卫视中文台", 
            "sop://broker.100vod.com:3912/1028":"凤凰卫视新闻台"}
        self.channels = {}
        self.groups = {}

    def parse_channel(self, file):
        self.channels = {}
        self.groups = {}
        d = xml.dom.minidom.parse(file)
        for g in d.documentElement.getElementsByTagName('group'):
            self.groups[g] = []
            for c in g.childNodes[1:]:
                for i in c.getElementsByTagName('item'):
                    if len(i.childNodes) == 1:
                        url =i.childNodes[0].nodeValue
                        self.groups[g].append(url)
                        self.channels[url] = c

    def UpdateChannels(self):
        try:
            pout = urllib2.urlopen(self.channel_list_url)
            self.parse_channel(pout)
            pout.close()
        except Exception, e:
            dlg = wx.MessageDialog(None, str(e), 'Error', wx.OK | wx.ICON_ERROR)
            dlg.ShowModal()
            dlg.Destroy()


    def Save(self):
        d = os.path.join(os.environ.get("HOME", "/tmp"), ".wxsopcast")
        if not os.path.exists(d):
            os.makedirs(d)
        f = file(os.path.join(d, "conf"), "w")
        pickle.dump(self, f)
        f.close()
    
    @staticmethod
    def Load():
        d = os.path.join(os.environ.get("HOME", "/tmp"), ".wxsopcast")
        if not os.path.exists(d):
            os.makedirs(d)
        try:
            f = file(os.path.join(d, "conf"), "r")
            config = pickle.load(f)
            f.close()
            if not hasattr(config, 'channels'):
                config.UpdateChannels()
            if hasattr(config, 'channels_name'):
                del config.channels_name
            return config
        except:
            return Config()
        

class SopCast:
    def __init__(self, config, url, channel):
        self.pid_player = 0
        self.pid_sop = 0
        self.config = config
        self.url = url
        self.channel = channel
        if self.channel:
            self.name = channel.getElementsByTagName('name')[0].childNodes[0].nodeValue
        else:
            self.name = ""
                                
    def KillPlayer(self):
        if self.pid_player > 0:
            try:
                os.kill(self.pid_player, signal.SIGTERM)
            except:
                pass
            time.sleep(1)
            if self.pid_player > 0:
                try:
                    os.kill(self.pid_player, signal.SIGKILL)
                except:
                    pass
            self.pid_player = 0
            
    def KillSop(self):
        self.KillPlayer()
        if self.pid_sop > 0:
            try:
                os.kill(self.pid_sop, signal.SIGTERM)
            except:
                pass
            time.sleep(1)
            if self.pid_sop >0:
                try:
                    os.kill(self.pid_sop, signal.SIGKILL)
                except:
                    pass
            self.pid_sop = 0
    
    def pick_unused_port(self):
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.bind(('localhost', 0))
        addr, port = s.getsockname()
        s.close()
        return port
            
    def RestartSop(self):
        self.KillSop()
        self.inport = self.pick_unused_port()
        self.outport = self.pick_unused_port()
        self.pid_sop = os.fork()
        if self.pid_sop == 0:
            fd = os.open("/dev/null", os.O_WRONLY)
            os.dup2(fd, 1)
            os.execlp(self.config.sop , self.config.sop, self.url, str(self.inport), str(self.outport))
            sys.exit(-1)
        wx.FutureCall(10000, self.RestartPlayer)

    def RestartPlayer(self):
        if self.pid_sop <= 0:
            return
        self.KillPlayer()
        self.pid_player = os.fork()
        if self.pid_player == 0:
            player_opt = self.config.player.split()
            player_opt.append("http://127.0.0.1:%d"%self.outport)
            fd = os.open("/dev/null", os.O_WRONLY)
            os.dup2(fd, 1)
            os.dup2(fd, 2)
            os.execlp(player_opt[0], *player_opt)
            sys.exit(-1)

class InfoDialog(wx.Dialog):
    def __init__(self, parent, sopcast):
        wx.Dialog.__init__(self, parent, title = "Info")
        vbox = wx.BoxSizer(wx.VERTICAL)
        
        grid = wx.FlexGridSizer(3, 2)
        grid.AddGrowableCol(1)
        vbox.Add(grid, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5)
        
        #name
        grid.Add(wx.StaticText(self, label = "Name:"), flag = wx.ALIGN_CENTER|wx.ALL, border = 5)
        grid.Add(wx.TextCtrl(self, value = sopcast.name, style = wx.TE_READONLY), proportion = 1, flag = wx.ALIGN_CENTER|wx.EXPAND|wx.ALL, border = 5)
        
        #url
        grid.Add(wx.StaticText(self, label = "URL:"), flag = wx.ALIGN_CENTER|wx.ALL, border = 5)
        grid.Add(wx.TextCtrl(self, value = sopcast.url, style = wx.TE_READONLY, size=(300,-1)), proportion = 1, flag = wx.ALIGN_CENTER|wx.ALL, border = 5)
        
        #id
        grid.Add(wx.StaticText(self, label = "ID:"), flag = wx.ALIGN_CENTER|wx.ALL, border = 5)
        grid.Add(wx.TextCtrl(self, value = sopcast.channel.getAttribute('id'), style = wx.TE_READONLY, size=(300,-1)), proportion = 1, flag = wx.ALIGN_CENTER|wx.ALL, border = 5)

        #class
        grid.Add(wx.StaticText(self, label = "class"), flag = wx.ALIGN_CENTER|wx.ALL, border = 5)
        try:
            elems = sopcast.channel.getElementsByTagName('class')
            value = elems[0].getAttribute('cn')
        except:
            value = ''
        grid.Add(wx.TextCtrl(self, value = value, style = wx.TE_READONLY, size=(300,-1)), proportion = 1, flag = wx.ALIGN_CENTER|wx.ALL, border = 5)
        

        #description
        grid.Add(wx.StaticText(self, label = "Description:"), flag = wx.ALIGN_CENTER|wx.ALL, border = 5)
        try:
            elems = sopcast.channel.getElementsByTagName('description')
            description = elems[0].childNodes[0].nodeValue
        except:
            description = ''
        grid.Add(wx.TextCtrl(self, value = description, style = wx.TE_READONLY|wx.TE_MULTILINE, size=(300,-1)), proportion = 1, flag = wx.ALIGN_CENTER|wx.ALL, border = 5)


        btnsizer = wx.StdDialogButtonSizer()
        btn = wx.Button(self, wx.ID_OK)
        btn.SetDefault()
        btnsizer.AddButton(btn)
        btnsizer.Realize()
        vbox.Add(btnsizer, 0, wx.ALIGN_RIGHT| wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5)

        self.SetSizer(vbox)
        vbox.Fit(self)
     

class OptionDialog(wx.Dialog):
    def __init__(self, parent, config):
        wx.Dialog.__init__(self, parent, title = "Options")
        self.config = config
        vbox = wx.BoxSizer(wx.VERTICAL)
        
        #channel list option
        hbox = wx.BoxSizer(wx.HORIZONTAL)
        label = wx.StaticText(self, -1, "URL of channel list:")
        hbox.Add(label, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
        self.channel_list_url = wx.TextCtrl(self, -1, self.config.channel_list_url, size=(250,-1))
        hbox.Add(self.channel_list_url, 1, wx.ALIGN_CENTRE|wx.ALL, 5)
        vbox.Add(hbox, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5)
        
        #player option
        hbox = wx.BoxSizer(wx.HORIZONTAL)
        label = wx.StaticText(self, -1, "player command:")
        hbox.Add(label, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
        self.player = wx.TextCtrl(self, -1, self.config.player, size=(200,-1))
        hbox.Add(self.player, 1, wx.ALIGN_CENTRE|wx.ALL, 5)
        vbox.Add(hbox, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5)
    
        #sopcast engine option
        hbox = wx.BoxSizer(wx.HORIZONTAL)
        label = wx.StaticText(self, -1, "sopcast_engine command:")
        hbox.Add(label, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
        self.sop = wx.TextCtrl(self, -1, self.config.sop, size=(200,-1))
        hbox.Add(self.sop, 1, wx.ALIGN_CENTRE|wx.ALL, 5)
        vbox.Add(hbox, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5)

        btnsizer = wx.StdDialogButtonSizer()
        btn = wx.Button(self, wx.ID_OK)
        self.Bind(wx.EVT_BUTTON, self.OnOK, btn)
        btn.SetDefault()
        btnsizer.AddButton(btn)
        btn = wx.Button(self, wx.ID_CANCEL)
        btnsizer.AddButton(btn)
        btnsizer.Realize()
        vbox.Add(btnsizer, 0, wx.ALIGN_RIGHT| wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5)

        self.SetSizer(vbox)
        vbox.Fit(self)

    def OnOK(self, event):
        self.config.channel_list_url = self.channel_list_url.GetValue()
        self.config.player = self.player.GetValue()
        self.config.sop = self.sop.GetValue()
        self.config.Save()
        self.EndModal(wx.ID_OK)
        
class SopTaskBarIcon(wx.TaskBarIcon):
    def __init__(self):
        wx.TaskBarIcon.__init__(self)
        icon = self.GetIcon()
        self.SetIcon(icon, "%s %s"%(name ,version))
        self.channels_menuitems = {}
        self.config = Config.Load()
        self.players = {}
#        if self.config.channels_name == {}:
        if not self.config.channels:
            wx.CallAfter(self.config.UpdateChannels)
        signal.signal(signal.SIGCHLD, self.OnChildDeath)
        if len(sys.argv) > 1:
            for url in sys.argv[1:]:
                if url not in self.players:
                    sopcast = SopCast(self.config, url, self.config.channels.get(url, None))
                    self.players[url] = sopcast
                else:
                    sopcast = self.players[url]
                sopcast.RestartSop()
      

    def OnChildDeath(self, signum, frame):
        while True:
            try:
                pid, status = os.waitpid(-1, os.WNOHANG)
            except:
                return
            if pid > 0:
                for i in self.players.itervalues():
                    if pid == i.pid_player:
                        i.pid_player = 0
                    if pid == i.pid_sop:
                        i.pid_sop = 0
            else:
                return

    def CreateChannelMenu(self):
        menu = wx.Menu()
        self.channels_menuitems = {}
        for g, urlist in self.config.groups.iteritems():
            submenu = wx.Menu()
            for url in urlist:
                channel = self.config.channels[url]
                mi = submenu.Append(wx.ID_ANY, channel.getElementsByTagName('name')[0].childNodes[0].nodeValue)
                self.Bind(wx.EVT_MENU, self.OnTaskBarChannel, mi)
                self.channels_menuitems[mi.GetId()] = url
            menu.AppendMenu(wx.ID_ANY, g.childNodes[0].nodeValue, submenu)
        return menu

    def CreatePlayerMenu(self, sopcast):
        menu = wx.Menu()
        def Info(e):
            d = InfoDialog(None, sopcast)
            d.ShowModal()
            d.Destroy()
        self.Bind(wx.EVT_MENU, Info, menu.Append(wx.ID_ANY, "Info"))
        def Shutdown(e):
            sopcast.KillSop()
            del self.players[sopcast.url]
        self.Bind(wx.EVT_MENU, Shutdown, menu.Append(wx.ID_ANY, "Shutdown Channel"))
        def RestartSop(e):
            sopcast.RestartSop()
        self.Bind(wx.EVT_MENU, RestartSop, menu.Append(wx.ID_ANY, "Restart Channel" if sopcast.pid_sop > 0 else "Start Channel"))
        def RestartPlayer(e):
            sopcast.RestartPlayer()
        self.Bind(wx.EVT_MENU, RestartPlayer, menu.Append(wx.ID_ANY, "Restart Player" if sopcast.pid_player >0 else "Start Player"))
        menu.Append(wx.ID_ANY, "Record")
        return menu

    def CreatePopupMenu(self):
        menu = wx.Menu()
        for url, i in self.players.iteritems():
            menu.AppendMenu(wx.ID_ANY, i.name, self.CreatePlayerMenu(i))
        menu.AppendSeparator()
        menu.AppendMenu(wx.ID_ANY, "Play a channel", self.CreateChannelMenu())
        self.Bind(wx.EVT_MENU, self.OnPlayCustomURL, menu.Append(wx.ID_ANY, "Play custom URL"))
        self.Bind(wx.EVT_MENU, self.OnTaskBarUpdate, menu.Append(wx.ID_ANY, "Update channels"))
        self.Bind(wx.EVT_MENU, self.OnTaskBarOption, menu.Append(wx.ID_ANY, "Option"))
        self.Bind(wx.EVT_MENU, self.OnTaskBarDebug, menu.Append(wx.ID_ANY, "Debug"))
        self.Bind(wx.EVT_MENU, self.OnTaskBarAbout, menu.Append(wx.ID_ANY, "About"))
        self.Bind(wx.EVT_MENU, self.OnTaskBarExit, menu.Append(wx.ID_ANY, "Exit"))
        return menu

    def GetIcon(self):
        stream = cStringIO.StringIO(icon_image)
        img = wx.ImageFromStream(stream)
        img = img.Scale(22, 22)
        icon = wx.IconFromBitmap(img.ConvertToBitmap() )
        return icon
    
    def OnTaskBarChannel(self, event):
        id = event.GetId()
        url = self.channels_menuitems[id]
        if not url in self.players:
            sopcast = SopCast(self.config, url, self.config.channels.get(url, None))
            self.players[url] = sopcast
        else:
            sopcast = self.players[url]
        sopcast.RestartSop()

    def OnPlayCustomURL(self, event):
        dlg = wx.TextEntryDialog(None, 'sopcast URL(start with sop://)',
                'Play Custom URL', 'Python')
        dlg.SetValue("sop://")
        if dlg.ShowModal() == wx.ID_OK:
            url = dlg.GetValue()
            if url not in self.players:
                sopcast = SopCast(self.config, url, self.config.channels.get(url, None))
                self.players[url] = sopcast
            else:
                sopcast = self.players[url]
            sopcast.RestartSop()
        dlg.Destroy()
        
    def OnTaskBarExit(self, evt):
        self.config.Save()
        for i in self.players.keys():
            self.players[i].KillSop()
        wx.CallAfter(self.Destroy)
    def OnTaskBarOption(self, evt):
        dlg = OptionDialog(None, self.config)
        dlg.Center()
        dlg.ShowModal()
        dlg.Destroy()
    def OnTaskBarDebug(self, evt):
        frame = wx.Frame(None, size=(640,480))
        w = wx.py.shell.Shell(frame)
        frame.Center()
        frame.Show()
        
    def OnTaskBarUpdate(self, event):
        self.config.UpdateChannels()
    def OnTaskBarAbout(self, event):
        info = wx.AboutDialogInfo()
        info.Name = name
        info.Version = version
        info.Copyright = "(C) 2007 czk"
        info.Description = "A sopcast GUI that written in python and wxPython "
        info.WebSite = ("http://code.google.com/p/wxsopcast/", "wxsopcast home page")
        info.Developers = [ "czk", ]
        info.License = "GPL 2.0"
        wx.AboutBox(info)


if __name__ == '__main__':
    app = wx.App()
    taskbaricon = SopTaskBarIcon()
    app.MainLoop()
